package fr.machizaud.research.atltransformer.app;

import fr.machizaud.research.atltransformer.ATLEngine;

import java.io.*;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;

import static fr.machizaud.research.atltransformer.app.App.AppParameter.*;

/**
 * ATL Transformer launcher
 */
public final class App {

    private static final Logger logger = Logger.getLogger(App.class.getCanonicalName());
    private static final PrintStream DEFAULT_OUTPUT = System.out;
    private Properties parameters = new Properties(System.getProperties());

    private static class AppHolder {

        private static final App instance = new App();
    }

    private App() {
        // Read logger configuration file
        try {
            LogManager.getLogManager().readConfiguration(
                    getClass().getClassLoader().getResourceAsStream("logging.properties"));
            logger.log(
                    Level.INFO,
                    "Logging configuration file successfully loaded");
        } catch (Exception e) {
            logger.log(
                    Level.SEVERE, "Unable to read logging configuration file",
                    e);
        }
    }

    public static App getInstance() {
        return AppHolder.instance;
    }

    public void checkRequirements() throws ApplicationException {
        if (
                parameters.getProperty(IN_MODEL.getValue()) == null
                        || parameters.getProperty(OUT_MODEL.getValue()) == null
                        || parameters.getProperty(RESOURCE_FILE.getValue()) == null
                        || parameters.getProperty(ATL_FILE.getValue()) == null
                )
            throw new ApplicationException("Prerequisites not met");
    }

    public OutputStream getOutputStream(String property) throws FileNotFoundException {
        if (property.equals("System.out")) {
            logger.log(Level.FINE, "You ask to redirect output to {0}", property);
            return DEFAULT_OUTPUT;
        } else if (property.startsWith("file://")) {
            property = property.substring("file://".length());
            logger.log(Level.FINE, "You ask to redirect output to {0}", property);
            try {
                File f = new File(property);
                if (!f.exists())
                    f.createNewFile();
                return new FileOutputStream(property);
            } catch (IOException e) {
                logger.log(Level.SEVERE, "Problem creating output file", e);
                return null;
            }
        } else {
            logger.log(Level.WARNING, "Can't understand the output you asked ({0}), redirecting output to {1}",
                    new String[]{property, DEFAULT_OUTPUT.getClass().getCanonicalName()});
            return DEFAULT_OUTPUT;
        }
    }

    private synchronized void load(InputStream reader) throws IOException {
        parameters.load(reader);
    }

    public String getProperty(String key) {
        return parameters.getProperty(key);
    }

    public String getProperty(String key, String defaultValue) {
        return parameters.getProperty(key, defaultValue);
    }

    public void launch() {
        try {
            // check consistency
            checkRequirements();
            logger.info("Prerequisites met");

            ATLEngine engine = ATLEngine.getEngine();
            logger.log(Level.FINE, "\n" +
                    "\t-In model : {0}\n" +
                    "\t-Out mode : {1}\n" +
                    "\t-Output   : {2}\n" +
                    "\t-Source   : {3}\n" +
                    "\t-ATL File : {4}",
                    new String[]{
                            getProperty(IN_MODEL.getValue()),
                            getProperty(OUT_MODEL.getValue()),
                            getProperty(OUTPUT_STREAM.getValue(), "System.out"),
                            getProperty(RESOURCE_FILE.getValue()),
                            getProperty(ATL_FILE.getValue())
                    });

            logger.info("Launch transformation");
            engine.transform(
                    getProperty(IN_MODEL.getValue()),
                    getProperty(OUT_MODEL.getValue()),
                    getProperty(RESOURCE_FILE.getValue()),
                    getProperty(ATL_FILE.getValue()),
                    getOutputStream(getProperty(OUTPUT_STREAM.getValue(), "System.out"))
            );

            logger.info("Terminated successfully");
        } catch (ApplicationException ex) {
            logger.log(Level.SEVERE, "Wrong call, check parameters", ex);
        } catch (IOException ex) {
            logger.log(Level.SEVERE, "Error during configuration file analysis", ex);
        }
    }

    public static void main(String[] args) {
        App.getInstance().setEnvironment(args);
        App.getInstance().launch();
    }

    private void setEnvironment(String[] args) {
        logger.info("Loading application parameters...");
        // from a configuration file
        String propertyFile =
                getProperty(PROPERTIES_FILE.getValue(), "application.properties");
        logger.fine("Properties file : " + propertyFile);
        try {
            load(App.class.getClassLoader().getResource(propertyFile).openStream());
        } catch (FileNotFoundException ex) {
            logger.log(Level.SEVERE, "Given configuration file not found", ex);
        } catch (IOException ex) {
            logger.log(Level.SEVERE, "Error while parsing configuration file", ex);
        }
        for (String arg : args) {
            int indexOf = arg.indexOf("=");
            String value = (indexOf > 0) ? arg.substring(indexOf + 1) : "";
            if (arg.startsWith(OUTPUT_STREAM.getValue() + "=")) {
                parameters.put(OUTPUT_STREAM.getValue(), value);
            } else if (arg.startsWith(RESOURCE_FILE.getValue() + "=")) {
                parameters.put(RESOURCE_FILE.getValue(), value);
            } else if (arg.startsWith(IN_MODEL.getValue() + "=")) {
                parameters.put(IN_MODEL.getValue(), value);
            } else if (arg.startsWith(OUT_MODEL.getValue() + "=")) {
                parameters.put(OUT_MODEL.getValue(), value);
            } else if (arg.startsWith(ATL_FILE.getValue() + "=")) {
                parameters.put(ATL_FILE.getValue(), value);
            }
        }
        logger.finer("Parameters : " +parameters.toString());
    }

    /**
     * Enumerate all possible parameters for this application with key bounds to it
     */
    public enum AppParameter {
        /**
         * application configuration file
         */
        PROPERTIES_FILE("properties"),
        /**
         * OutputStream where to print the transformation result
         */
        OUTPUT_STREAM("output"),
        /**
         * The resource to transform
         */
        RESOURCE_FILE("resource"),
        /**
         * Name of the in model
         */
        IN_MODEL("in.model"),
        /**
         * Name of the out model
         */
        OUT_MODEL("out.model"),
        /**
         * ATL file which contains all transformations rules
         */
        ATL_FILE("atl-rules");

        private String name;

        private AppParameter(String value) {
            this.name = value;
        }

        public String getValue() {
            return name;
        }
    }
}
